package insight.sm.dao;


import insight.sm.db.EntityRowMapper;

import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import javax.persistence.Column;
import javax.persistence.Entity;

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;


public abstract class GenericDAOImpl<T> implements IGenericDAO<T> {
	@Autowired
	private JdbcTemplate jdbcTemplate;

	public int insertRecord(T instance)  {
		try{
			@SuppressWarnings("rawtypes")
			Class c=Class.forName(instance.getClass().getName());
			LinkedList<String> argumentName=new LinkedList<String>();
			LinkedList<String> argumentVal=new LinkedList<String>();
			LinkedList<String> argumentType=new LinkedList<String>();
			
			LinkedList<String> placeHolder=new LinkedList<String>();
			Field[] fields=instance.getClass().getDeclaredFields();
			
			for(Field field:fields){
				if(field.getAnnotation(Column.class)!=null){
					if(field.getAnnotation(Column.class).unique()==true){
						
					} else if (field.getAnnotation(Column.class).insertable()==false){
					
					} else if (field.getAnnotation(Column.class).name().length()>0){
						argumentName.add(field.getAnnotation(Column.class).name());
						placeHolder.add("?");
						argumentVal.add(PropertyUtils.getProperty(instance, field.getName()).toString());
						argumentType.add(PropertyUtils.getPropertyType(instance, field.getName()).toString());
					} else {
						argumentName.add(field.getName());
						placeHolder.add("?");
						argumentVal.add(PropertyUtils.getProperty(instance, field.getName()).toString());
						argumentType.add(PropertyUtils.getPropertyType(instance, field.getName()).toString());
					}
				}else
				{
					argumentName.add(field.getName());
					placeHolder.add("?");
					argumentVal.add(PropertyUtils.getProperty(instance, field.getName()).toString());
					argumentType.add(PropertyUtils.getPropertyType(instance, field.getName()).toString());
				}
			}
			@SuppressWarnings("unchecked")
			final String sql = "insert into "+getTableName(c)+" ("+StringUtils.join(argumentName.toArray(), ',')+") values ("+StringUtils.join(placeHolder.toArray(), ',')+")";
			System.out.println("SQL: "+sql);
			KeyHolder holder=new GeneratedKeyHolder();
			final LinkedList<String> passedVal=argumentVal;
			final LinkedList<String> passedType=argumentType;
			jdbcTemplate.update(new PreparedStatementCreator() {				
				@SuppressWarnings("deprecation")
				@Override
				public PreparedStatement createPreparedStatement(Connection cn) throws SQLException {
					PreparedStatement preparedStatement = cn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
					int col=1;
					for(int i=0;i<passedVal.size();i++){
						
						String type=passedType.get(i);
						switch(type){
							case "class java.lang.String":
								preparedStatement.setString(col, passedVal.get(i));
								col++;
								break;
							case "int":
								preparedStatement.setInt(col, Integer.parseInt(passedVal.get(i)));
								col++;
								break;	
							case "class java.util.Date":
								//preparedStatement.setObject(col, passedVal.get(i));
								preparedStatement.setDate(col, new java.sql.Date(new java.util.Date(passedVal.get(i)).getTime()));
								col++;
								break;
							default:
								System.out.println(type+" Not found");
						}
						
					}
					return preparedStatement;
				}
			},holder);
			return holder.getKey().intValue();
		}catch(Exception e){e.printStackTrace();return 0;}
	}


public int updateRecord(T instance)  {
		try{
			@SuppressWarnings("rawtypes")
			Class c=Class.forName(instance.getClass().getName());
			LinkedList<String> argumentName=new LinkedList<String>();
			LinkedList<String> argumentVal=new LinkedList<String>();
			LinkedList<String> argumentType=new LinkedList<String>();
			
			Field[] fields=instance.getClass().getDeclaredFields();
			for(Field field:fields){
				if(field.getAnnotation(Column.class)!=null){
					if(field.getAnnotation(Column.class).unique()==true){
					} else if (field.getAnnotation(Column.class).insertable()==false){
					
					} else if (field.getAnnotation(Column.class).name().length()>0){
						argumentName.add(field.getAnnotation(Column.class).name()+" = ?");
						argumentVal.add(PropertyUtils.getProperty(instance, field.getName()).toString());
						argumentType.add(PropertyUtils.getPropertyType(instance, field.getName()).toString());
					} else {
						
					}
				} else {
					argumentName.add(field.getName()+" = ?");
					argumentVal.add(PropertyUtils.getProperty(instance, field.getName()).toString());
					argumentType.add(PropertyUtils.getPropertyType(instance, field.getName()).toString());
				}
			}
			
			@SuppressWarnings("unchecked")
			final String sql ="update "+getTableName(c)+" set "+StringUtils.join(argumentName.toArray()," , ")+" where "+getUniqueColumnAnnotation(c)+" = ?";
			@SuppressWarnings("unchecked")
			String key=PropertyUtils.getProperty(instance, getUniqueColumnName(c)).toString();
			@SuppressWarnings("unchecked")
			String keyType=PropertyUtils.getPropertyType(instance, getUniqueColumnName(c)).toString();
			argumentVal.add(key);
			argumentType.add(keyType);
			final LinkedList<String> passedVal=argumentVal;
			final LinkedList<String> passedType=argumentType;
			
			return jdbcTemplate.update(new PreparedStatementCreator() {				
				@Override
				public PreparedStatement createPreparedStatement(Connection cn) throws SQLException {
					PreparedStatement preparedStatement = cn.prepareStatement(sql);
					int col=1;
					for(int i=0;i<passedVal.size();i++){			
						String type=passedType.get(i);
						//System.out.println("-----"+passedVal.get(i)+": "+passedType.get(i));
						switch(type){
							case "class java.lang.String":
								preparedStatement.setObject(col, passedVal.get(i));
								col++;
								break;
							case "int":
								preparedStatement.setInt(col, Integer.parseInt(passedVal.get(i)));
								col++;
								break;	
							case "class java.util.Date":
								preparedStatement.setObject(col, passedVal.get(i));
								col++;
								break;
							default:
								System.out.println(type+" Not found");
						}		
					}
					return preparedStatement;
				}
			});
		}catch(Exception e){e.printStackTrace();return 0;}
	}

	
	private String getTableName(Class<T> clazz) {
		  Entity entityAnnotation = clazz.getAnnotation(Entity.class);
		  if (entityAnnotation != null  && org.springframework.util.StringUtils.hasText(entityAnnotation.name()))
		   return entityAnnotation.name();
		  return clazz.getSimpleName();
	}
	
	private String getActualColumName(Class<T> clazz, String columnName) {
		Field[] fields = clazz.getDeclaredFields();
		for(Field field:fields){
			if(field.getAnnotation(Column.class)!=null){
				if(field.getAnnotation(Column.class).name().equals(columnName)){
					return field.getName();
				}
			}
		}
		return columnName;
	}

	private String getUniqueColumnAnnotation(Class<T> clazz) {
		Field[] fields = clazz.getDeclaredFields();
		for(Field field:fields){
			if(field.getAnnotation(Column.class)!=null){
				if(field.getAnnotation(Column.class).unique()==true){
					return field.getAnnotation(Column.class).name();
				}
			}
		}
		return null;
	}

	
	private String getUniqueColumnName(Class<T> clazz) {
		Field[] fields = clazz.getDeclaredFields();
		for(Field field:fields){
			if(field.getAnnotation(Column.class)!=null){
				if(field.getAnnotation(Column.class).unique()==true){
					return field.getName();
				}
			}
		}
		return null;
	}

	@Override
	public T findById(Class<T> entityClass, int id) {
		return (T) jdbcTemplate.queryForObject("select * from "+getTableName(entityClass)+" where "+getUniqueColumnAnnotation(entityClass)+" = "+id, new EntityRowMapper<>(entityClass));
	}
	
	@Override
	public void update(T instance) {
		System.out.println(updateRecord(instance));
	}

	@Override
	public int delete(Class<T> entityClass, int id) {
		return jdbcTemplate.update("delete from "+getTableName(entityClass)+" where id="+id);
	}

	
	@Override
	public int deleteAll(Class<T> entityClass) {
		return jdbcTemplate.update("delete from "+getTableName(entityClass));
	}

	@Override
	public void save(T instance) {
		System.out.println(insertRecord(instance));
	}

	@Override
	public List<T> getList(Class<T> entity) {
		return jdbcTemplate.query("select * from "+getTableName(entity), new EntityRowMapper<>(entity));
	}

	@Override
	public List<String> getMetadata(Class<T> entity) {
		return jdbcTemplate.queryForList("select column_name from user_tab_columns where table_name='"+getTableName(entity)+"'",String.class);		
	}

	@Override
	public List<String> getColumnHeader(Class<T> entity) {
		List<String> arrayList=new ArrayList<String>();
		Field[] fields=entity.getDeclaredFields();
		for(Field f:fields){
			arrayList.add(f.getName());
		}
		return arrayList;		
	}

	
	@Override
	public List<T> getListCustom(Class<T> entity, String customQuery) {
		return jdbcTemplate.query("select * from "+getTableName(entity)+" where " + customQuery,  new EntityRowMapper<>(entity));
	}


	@Override
	public List<T> findByProperty(Class<T> entity, String paramName, Object value) {
		return jdbcTemplate.query("select * from "+getTableName(entity)+" where " + getActualColumName(entity,paramName)+" = "+value, new EntityRowMapper<>(entity));
	}	
}
